package com.zmx.core.client.invokemethod;

import com.zmx.core.client.log.LogInfoHandler;
import com.zmx.core.dto.RequestDataInfo;
import com.zmx.core.dto.RequestLogInfo;
import com.zmx.core.dto.ReturnT;
import com.zmx.core.executor.JobExecutor;
import com.zmx.core.handler.MethodJobHandler;
import com.zmx.core.utils.AESUtil;
import com.zmx.core.utils.JacksonUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Objects;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Description: 客户端执行任务处理类
 * @ClassName: InvokeMethodHandler
 * @Author zhaomxr
 * @Date 2021-12-17 15:05
 */
@Slf4j
public class InvokeMethodHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private String username;
    private String password;
    private String secret;
    private ThreadPoolExecutor bizThreadPool;

    public InvokeMethodHandler() {
    }

    public InvokeMethodHandler(String username, String password, String secret, ThreadPoolExecutor bizThreadPool) {
        this.username = username;
        this.password = password;
        this.secret = secret;
        this.bizThreadPool = bizThreadPool;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
        if (fullHttpRequest != null) {
            String requestData = fullHttpRequest.content().toString(CharsetUtil.UTF_8);
            String uri = fullHttpRequest.uri();
            HttpMethod httpMethod = fullHttpRequest.method();
            boolean keepAlive = HttpUtil.isKeepAlive(fullHttpRequest);

            if (!StringUtils.isEmpty(requestData)) {
                //数据转换为RequestDataInfo
                RequestDataInfo requestDataInfo = JacksonUtil.jsonStringToBean(requestData, RequestDataInfo.class);

                //加密验证
                if (!check(ctx, keepAlive, requestDataInfo)) {
                    log.error("admin auth failed requestData:{} uri:{} method:{}", requestData, uri, httpMethod.name());
                    return;
                }

                //-----------------------判断是否是日志类型-------------------
                getLog(ctx, keepAlive, requestDataInfo);

                //-----------------------判断是否是执行任务-------------------
                executeMethod(ctx, keepAlive, requestDataInfo);
            }
        }
    }

    /**
     * 加密验证
     *
     * @param ctx
     * @param keepAlive
     * @param requestDataInfo
     * @throws Exception
     */
    private boolean check(ChannelHandlerContext ctx, boolean keepAlive, RequestDataInfo requestDataInfo) throws Exception {
        String verification = requestDataInfo.getVerification();
        String aesDecrypt = AESUtil.aesDecrypt(verification, secret);
        if (!Objects.equals(aesDecrypt, username + password)) {
            writeResponse(ctx, keepAlive, JacksonUtil.toJsonString(new ReturnT(ReturnStatusEnum.ERROR.getCode(), "auth failed")));
            return false;
        }
        return true;
    }

    /**
     * 取当前实例日志
     *
     * @param requestDataInfo
     */
    private void getLog(ChannelHandlerContext ctx, boolean keepAlive, RequestDataInfo requestDataInfo) {
        //-----------------------判断是否是日志类型-------------------
        if (Objects.equals(requestDataInfo.getType(), "log")) {
            LogInfoHandler logInfoService = new LogInfoHandler();
            RequestLogInfo requestLogInfo = JacksonUtil.jsonStringToBean(JacksonUtil.toJsonString(requestDataInfo.getParams()), RequestLogInfo.class);

            RequestLogInfo resultLog = logInfoService.getLog(requestLogInfo);
            writeResponse(ctx, keepAlive, JacksonUtil.toJsonString(new ReturnT(resultLog)));
        }
    }

    /**
     * 执行任务
     *
     * @param ctx
     * @param keepAlive
     * @param requestDataInfo
     */
    private void executeMethod(ChannelHandlerContext ctx, boolean keepAlive, RequestDataInfo requestDataInfo) {
        if (Objects.equals(requestDataInfo.getType(), "method")) {
            //判断当前任务是否加入队列，以达到最佳性能
            if (requestDataInfo.isDelayed()) {
                //当前任务加入队尾
                //MethodQueue.offer(requestDataInfo);
                //return;
            }

            String name = requestDataInfo.getValue();

            // 如果任务正在执行，则无法重新执行
            if (JobExecutor.isRunning(name)) {
                log.info(">>>>>>>>>>> job: {} is running!", name);
                writeResponse(ctx, keepAlive, JacksonUtil.toJsonString(new ReturnT(ReturnStatusEnum.ISRUNNING.getCode(), String.format(ReturnStatusEnum.ISRUNNING.getMsg(), name))));
                return;
            }

            bizThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        MethodJobHandler methodJobHandler = (MethodJobHandler) JobExecutor.loadJobHandler(name);
                        if (methodJobHandler == null) {
                            log.info("job: [{}] not exit!", name);
                            writeResponse(ctx, keepAlive, JacksonUtil.toJsonString(new ReturnT(ReturnStatusEnum.ERROR.getCode(), "job: [" + name + "] not exit!")));
                            return;
                        }

                        JobExecutor.registRunningJobHandler(name, methodJobHandler);

                        log.info(">>>>>>>>>>> job: {} start success!", name);
                        writeResponse(ctx, keepAlive, JacksonUtil.toJsonString(new ReturnT(ReturnStatusEnum.OK.getCode(), "job: [" + name + "] start success!")));

                        //执行目标方法
                        methodJobHandler.execute();
                    } catch (Exception e) {
                        writeResponse(ctx, keepAlive, JacksonUtil.toJsonString(new ReturnT(ReturnStatusEnum.ERROR.getCode(), "job: [" + name + "] execute fail!")));
                        log.error("job {} execute fail！", name);
                        log.error(e.getMessage(), e);
                    } finally {
                        // 执行完成，从当前正在执行map中删除
                        JobExecutor.destoryRunningJobHandler(name);
                    }
                }
            });
        }
    }

    /**
     * write response
     */
    private void writeResponse(ChannelHandlerContext ctx, boolean keepAlive, String responseJson) {
        // write response
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(responseJson, CharsetUtil.UTF_8));   //  Unpooled.wrappedBuffer(responseJson)
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");       // HttpHeaderValues.TEXT_PLAIN.toString()
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        if (keepAlive) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.writeAndFlush(response);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        super.channelReadComplete(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error(">>>>>>>>>>> mall-job provider netty_http server caught exception", cause);
        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            ctx.channel().close();      // beat 3N, close if idle
            log.debug(">>>>>>>>>>> mall-job provider netty_http server close an idle channel.");
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}
